﻿/*
 
Celerity: Sensory Overload
Copright 2012 by Adam Hill, David Gannon, Thomas Tamblyn & Patrick Ytting
Contact: adam.hill@outlook.com
Website: adamhill.org
Licence: MS-Pl Licence
  
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Celerity.World.Particles
{
	public class ExplosionEmitter
	{
		private Vector3 position;
		private Color colour;
		private Color accentColour;
		public bool isAlive;
		private float startTime;
		private float duration;
		private float elapsedTime;
		private float particlesPerSecond;
		private Random rand;
		private ParticleFactory particleFactory;
		private int accentProbability;
		private float velocityMagMax;
		private float velocityMagMin;

		public ExplosionEmitter(
			Vector3 position, 
			Color colour,
			float duration, 
			float particlePerSecond, 
			int accentProbability, 
			float velocityMagMax,
			float velocityMagMin,
			GameTime gameTime, 
			ParticleFactory particleFactory)
		{
			this.position = position;
			this.colour = colour;
			this.accentColour = GetComplimentaryColour(colour);
			this.isAlive = true;
			this.startTime = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;
			this.duration = duration;
			this.elapsedTime = 0.0f;
			this.particlesPerSecond = particlePerSecond;
			this.rand = new Random();
			this.particleFactory = particleFactory;
			this.accentProbability = accentProbability;
			this.velocityMagMax = velocityMagMax;
			this.velocityMagMin = velocityMagMin;
		}

		private Vector3 GetRandomUnitVector()
		{
			int precision = 10000;
			float pitch = (float)rand.Next(0, precision) / (float)precision;
			float roll = (float)rand.Next(0, precision) / (float)precision;
			float yaw = (float)rand.Next(0, precision) / (float)precision;
			return Vector3.Transform(new Vector3(0.0f, 1.0f, 0.0f), Matrix.CreateFromYawPitchRoll(yaw * (float)Math.PI * 2.0f, pitch * (float)Math.PI * 2.0f, roll * (float)Math.PI * 2.0f));
		}

		private float GetRandomMagnitude()
		{
			int precision = 10000;
			float delta = velocityMagMax - velocityMagMin;
			float r = ((float)rand.Next(0, precision) / (float)precision) * delta;
			return velocityMagMin + r;
		}

		#region ColorFunctions
		private Color GetComplimentaryColour(Color colour)
		{
            return Color.Black;
            //Color hsl = ToHSL(colour);
            //hsl.R += 128 % 255;
            //return ToRGB(hsl);
		}

		private Color ToHSL(Color colour)
		{
			float R = colour.R / 255.0f;
			float G = colour.G / 255.0f;
			float B = colour.B / 255.0f;
			float min = Math.Min(R, G);
			min = Math.Min(min, B);
			float max = Math.Max(R, G);
			max = Math.Max(max, B);
			float delta = max - min;
			float L = 0.5f * (max + min);
			float H = 0.0f;
			float S = 0.0f;
			if (!nearlyEqual(delta, 0.0f))
			{
				if (L < 0.5f)
					S = delta / (max + min);
				else
					S = delta / (2 - max - min);
				float deltaR = (((max - R) / 6.0f) + (delta * 0.5f)) / delta;
				float deltaG = (((max - G) / 6.0f) + (delta * 0.5f)) / delta;
				float deltaB = (((max - B) / 6.0f) + (delta * 0.5f)) / delta;
				if (nearlyEqual(R, max))
					H = deltaB - deltaG;
				else if (nearlyEqual(G, max))
					H = (1.0f / 3.0f) + deltaR - deltaB;
				else if (nearlyEqual(B, max))
					H = (2.0f / 3.0f) + deltaG - deltaR;
				if (H < 0.0f) H += 1;
				if (H > 1.0f) H -= 1;
			}
			return new Color(H, S, L);
		}

		private Color ToRGB(Color colour)
		{
			float H = colour.R / 255.0f;
			float S = colour.G / 255.0f;
			float L = colour.B / 255.0f;
			float R = L;
			float G = L;
			float B = L;
			if (!nearlyEqual(S, 0.0f))
			{
				float v2;
				if (L < 0.5f)
					v2 = L * (1.0f + S);
				else
					v2 = (L + S) - (S * L);
				float v1 = 2.0f * L - v2;
				R = HueCalc(v1, v2, H + (1.0f / 3.0f));
				G = HueCalc(v1, v2, H);
				B = HueCalc(v1, v2, H - (1.0f / 3.0f));
			}
			return new Color(R, G, B);
		}

		private float HueCalc(float v1, float v2, float vH)
		{
			if (vH < 0.0f) vH += 1.0f;
			if (vH > 1.0f) vH -= 1.0f;
			if (6.0f * vH < 1.0f) return v1 + (v2 - v1) * 6.0f * vH;
			if (2.0f * vH < 1.0f) return v2;
			if (3.0f * vH < 2.0f) return v1 + (v2 - v1) * ((2.0f / 3.0f) - vH) * 6.0f;
			return v1;
		}

		public static bool nearlyEqual(float a, float b)
		{
			float epsilon = 1.0f / 255.0f;
			float absA = Math.Abs(a);
			float absB = Math.Abs(b);
			float diff = Math.Abs(a - b);

			if (a * b == 0) // a or b or both are zero relative error is not meaningful here
				return diff < (epsilon * epsilon);
			else // use relative error
				return diff / (absA + absB) < epsilon;
		}
		#endregion ColourFunctions

		private void CreateExplosionParticles(int numParticles)
		{
			for (int i = 0; i < numParticles; i++)
			{
				Color particleColour;
				if (rand.Next(0, accentProbability) == 0)
					particleColour = this.accentColour;
				else
					particleColour = this.colour;
				particleFactory.CreateExplosionParticle(this.position, GetRandomUnitVector() * GetRandomMagnitude(), particleColour);
			}
		}

		public void Update(GameTime gameTime)
		{
			float timeThisFrame = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000.0f;
			elapsedTime += timeThisFrame;
			isAlive = startTime + elapsedTime < startTime + duration;
			if (isAlive)
			{
				float particlesThisFrame = particlesPerSecond * timeThisFrame;
				CreateExplosionParticles((int)Math.Round(particlesThisFrame, 0));
			}
		}
	}
}
